#include "Config.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstring>
#include <algorithm>
#include <iomanip>
#include <map>
#include <filesystem>
#include <regex>

namespace fs = std::filesystem;

// Get singleton instance
Config& Config::getInstance() {
    static Config instance;
    return instance;
}

void Config::RinexSettings::expandFilePatterns() {
    // Process observation file patterns
    for (const auto& patternConfig : observationPatterns) {
        try {
            std::string patternDir = fs::path(patternConfig.pattern).parent_path().string();
            std::string patternFile = fs::path(patternConfig.pattern).filename().string();
            
            // Create directory if it doesn't exist
            if (!patternDir.empty() && !fs::exists(patternDir)) {
                continue; // Skip if directory doesn't exist
            }
            
            // Get base directory or use current directory if pattern is just a filename
            fs::path baseDir = patternDir.empty() ? fs::current_path() : fs::path(patternDir);
            
            // Process each matching file in the directory
            for (const auto& entry : fs::directory_iterator(baseDir)) {
                if (!entry.is_regular_file()) continue;
                
                std::string filename = entry.path().filename().string();
                
                // Simple wildcard matching (supporting only * as wildcard for simplicity)
                bool matches = true;
                if (patternFile != "*" && patternFile != "*.*") {
                    // Convert pattern to regex pattern
                    std::string regexPattern = "";
                    for (char c : patternFile) {
                        if (c == '*') regexPattern += ".*";
                        else if (c == '?') regexPattern += ".";
                        else if (c == '.' || c == '+' || c == '[' || c == ']' || c == '(' || c == ')' || c == '^' || c == '$') {
                            regexPattern += '\\';
                            regexPattern += c;
                        } else {
                            regexPattern += c;
                        }
                    }
                    
                    std::regex pattern(regexPattern);
                    matches = std::regex_match(filename, pattern);
                }
                
                if (matches) {
                    // Add file to observation files
                    FileConfig config;
                    config.path = entry.path().string();
                    config.enabledSystems = patternConfig.enabledSystems;
                    observationFiles.push_back(config);
                    
                    if (Config::getInstance().verbose) {
                        std::cout << "Added observation file from pattern: " << config.path << std::endl;
                    }
                }
            }
        } catch (const std::exception& e) {
            std::cerr << "Error expanding observation file pattern " << patternConfig.pattern << ": " << e.what() << std::endl;
        }
    }
    
    // Process navigation file patterns (similar logic)
    for (const auto& patternConfig : navigationPatterns) {
        try {
            std::string patternDir = fs::path(patternConfig.pattern).parent_path().string();
            std::string patternFile = fs::path(patternConfig.pattern).filename().string();
            
            // Skip if directory doesn't exist
            if (!patternDir.empty() && !fs::exists(patternDir)) {
                continue;
            }
            
            // Get base directory
            fs::path baseDir = patternDir.empty() ? fs::current_path() : fs::path(patternDir);
            
            // Process each matching file
            for (const auto& entry : fs::directory_iterator(baseDir)) {
                if (!entry.is_regular_file()) continue;
                
                std::string filename = entry.path().filename().string();
                
                // Simple wildcard matching
                bool matches = true;
                if (patternFile != "*" && patternFile != "*.*") {
                    // Convert pattern to regex pattern
                    std::string regexPattern = "";
                    for (char c : patternFile) {
                        if (c == '*') regexPattern += ".*";
                        else if (c == '?') regexPattern += ".";
                        else if (c == '.' || c == '+' || c == '[' || c == ']' || c == '(' || c == ')' || c == '^' || c == '$') {
                            regexPattern += '\\';
                            regexPattern += c;
                        } else {
                            regexPattern += c;
                        }
                    }
                    
                    std::regex pattern(regexPattern);
                    matches = std::regex_match(filename, pattern);
                }
                
                if (matches) {
                    // Add file to navigation files
                    FileConfig config;
                    config.path = entry.path().string();
                    navigationFiles.push_back(config);
                    
                    if (Config::getInstance().verbose) {
                        std::cout << "Added navigation file from pattern: " << config.path << std::endl;
                    }
                }
            }
        } catch (const std::exception& e) {
            std::cerr << "Error expanding navigation file pattern " << patternConfig.pattern << ": " << e.what() << std::endl;
        }
    }
    
    // Process SP3 file patterns
    for (const auto& pattern : sp3Patterns) {
        try {
            std::string patternDir = fs::path(pattern).parent_path().string();
            std::string patternFile = fs::path(pattern).filename().string();
            
            // Skip if directory doesn't exist
            if (!patternDir.empty() && !fs::exists(patternDir)) {
                continue;
            }
            
            // Get base directory
            fs::path baseDir = patternDir.empty() ? fs::current_path() : fs::path(patternDir);
            
            // Process each matching file
            for (const auto& entry : fs::directory_iterator(baseDir)) {
                if (!entry.is_regular_file()) continue;
                
                std::string filename = entry.path().filename().string();
                
                // Simple wildcard matching
                bool matches = true;
                if (patternFile != "*" && patternFile != "*.*") {
                    // Convert pattern to regex pattern
                    std::string regexPattern = "";
                    for (char c : patternFile) {
                        if (c == '*') regexPattern += ".*";
                        else if (c == '?') regexPattern += ".";
                        else if (c == '.' || c == '+' || c == '[' || c == ']' || c == '(' || c == ')' || c == '^' || c == '$') {
                            regexPattern += '\\';
                            regexPattern += c;
                        } else {
                            regexPattern += c;
                        }
                    }
                    
                    std::regex pattern(regexPattern);
                    matches = std::regex_match(filename, pattern);
                }
                
                if (matches) {
                    // We found a match, use this as the SP3 file
                    // (If multiple matches, the last one will be used)
                    sp3File = entry.path().string();
                    
                    if (Config::getInstance().verbose) {
                        std::cout << "Set SP3 file from pattern: " << sp3File << std::endl;
                    }
                }
            }
        } catch (const std::exception& e) {
            std::cerr << "Error expanding SP3 file pattern " << pattern << ": " << e.what() << std::endl;
        }
    }
}

bool Config::loadFromFile(const std::string& filename) {
    std::ifstream file(filename);
    if (!file.is_open()) {
        std::cerr << "Error: Could not open config file " << filename << std::endl;
        return false;
    }
    
    std::string line, section;
    std::map<std::string, std::string> obsFileMap;
    std::map<std::string, std::string> obsFileSystemsMap;
    std::map<std::string, std::string> obsPatternMap;
    std::map<std::string, std::string> obsPatternSystemsMap;
    
    while (std::getline(file, line)) {
        // Skip empty lines and comments
        if (line.empty() || line[0] == '#' || line[0] == ';') {
            continue;
        }
        
        // Handle section headers
        if (line[0] == '[' && line.back() == ']') {
            section = line.substr(1, line.size() - 2);
            continue;
        }
        
        // Parse key-value pairs
        size_t pos = line.find('=');
        if (pos == std::string::npos) {
            continue;
        }
        
        std::string key = line.substr(0, pos);
        std::string value = line.substr(pos + 1);
        
        // Trim whitespace
        key.erase(0, key.find_first_not_of(" \t"));
        key.erase(key.find_last_not_of(" \t") + 1);
        value.erase(0, value.find_first_not_of(" \t"));
        value.erase(value.find_last_not_of(" \t") + 1);
        
        if (section == "General") {
            if (key == "verbose") verbose = (value == "true" || value == "1");
            else if (key == "numThreads") numThreads = std::stoi(value);
            else if (key == "outputDir") outputDir = value;
        }
        else if (section == "RINEX") {
            if (key == "gnssSystems") {
                rinexSettings.gnssSystems.clear();
                std::istringstream iss(value);
                std::string system;
                while (std::getline(iss, system, ',')) {
                    rinexSettings.gnssSystems.push_back(system);
                }
            }
            else if (key.find("obsFile") == 0 && key.find("Pattern") == std::string::npos) {
                // Store observation files for later processing
                obsFileMap[key] = value;
            }
            else if (key.find("obsFile") == 0 && key.find("Systems") != std::string::npos && 
                     key.find("Pattern") == std::string::npos) {
                // Store systems for observation files
                obsFileSystemsMap[key] = value;
            }
            else if (key.find("obsFilePattern") == 0 && key.find("Systems") == std::string::npos) {
                // Store observation file patterns
                obsPatternMap[key] = value;
            }
            else if (key.find("obsFilePattern") == 0 && key.find("Systems") != std::string::npos) {
                // Store systems for observation file patterns
                obsPatternSystemsMap[key] = value;
            }
            else if (key.find("navFile") == 0 && key.find("Pattern") == std::string::npos) {
                rinexSettings.addNavigationFile(value);
            }
            else if (key.find("navFilePattern") == 0) {
                // Add navigation file pattern
                rinexSettings.addNavigationPattern(value);
            }
            else if (key == "sp3File") {
                rinexSettings.sp3File = value;
            }
            else if (key == "sp3FilePattern") {
                // Add SP3 file pattern
                rinexSettings.sp3Patterns.push_back(value);
            }
        }
        else if (section == "Preprocessing") {
            if (key == "minElevation") 
                preprocessingSettings.minElevation = std::stod(value);
            else if (key == "cycleSlipMethod") 
                preprocessingSettings.cycleSlipMethod = static_cast<CycleSlipMethod>(std::stoi(value));
            else if (key == "gfThreshold")
                preprocessingSettings.gfThreshold = std::stod(value);
            else if (key == "mwThreshold")
                preprocessingSettings.mwThreshold = std::stod(value);
            else if (key == "tdThreshold")
                preprocessingSettings.tdThreshold = std::stod(value);
            else if (key == "ionoRateThreshold")
                preprocessingSettings.ionoRateThreshold = std::stod(value);
            else if (key == "turboEditStrategy")
                preprocessingSettings.turboEditStrategy = 
                    static_cast<PreprocessingSettings::TurboEditStrategy>(std::stoi(value));
            else if (key == "adaptiveQualityThreshold")
                preprocessingSettings.adaptiveQualityThreshold = std::stod(value);
            else if (key == "fixCycleSlips")
                preprocessingSettings.fixCycleSlips = (value == "true" || value == "1");
            else if (key == "maxConsecutiveSlips")
                preprocessingSettings.maxConsecutiveSlips = std::stoi(value);
            else if (key == "maxTimeGap")
                preprocessingSettings.maxTimeGap = std::stod(value);
            else if (key == "enableMultipathDetection") 
                preprocessingSettings.enableMultipathDetection = (value == "true" || value == "1");
            else if (key == "multipathThreshold") 
                preprocessingSettings.multipathThreshold = std::stod(value);
            else if (key == "weightingMode") 
                preprocessingSettings.weightingMode = static_cast<WeightingMode>(std::stoi(value));
        }
        // Process other sections...
    }
    
    // Process observation files and their systems
    for (const auto& [key, path] : obsFileMap) {
        std::string systemsKey = key + "Systems";
        std::vector<std::string> systems;
        
        if (obsFileSystemsMap.find(systemsKey) != obsFileSystemsMap.end()) {
            std::string systemsStr = obsFileSystemsMap[systemsKey];
            std::istringstream iss(systemsStr);
            std::string system;
            while (std::getline(iss, system, ',')) {
                systems.push_back(system);
            }
        }
        
        rinexSettings.addObservationFile(path, systems);
    }
    
    // Process observation file patterns and their systems
    for (const auto& [key, pattern] : obsPatternMap) {
        std::string systemsKey = key + "Systems";
        std::vector<std::string> systems;
        
        if (obsPatternSystemsMap.find(systemsKey) != obsPatternSystemsMap.end()) {
            std::string systemsStr = obsPatternSystemsMap[systemsKey];
            std::istringstream iss(systemsStr);
            std::string system;
            while (std::getline(iss, system, ',')) {
                systems.push_back(system);
            }
        }
        
        rinexSettings.addObservationPattern(pattern, systems);
    }
    
    // Expand file patterns to actual files
    rinexSettings.expandFilePatterns();
    
    return true;
}

bool Config::saveToFile(const std::string& filename) const {
    std::ofstream file(filename);
    if (!file.is_open()) {
        std::cerr << "Error: Could not open file for writing: " << filename << std::endl;
        return false;
    }
    
    // Write general settings
    file << "# GNSS VTEC Estimation Configuration File\n";
    file << "# Generated automatically\n\n";
    
    file << "[General]\n";
    file << "verbose = " << (verbose ? "true" : "false") << "\n";
    file << "numThreads = " << numThreads << "\n";
    file << "outputDir = " << outputDir << "\n\n";
    
    // Write RINEX settings
    file << "[RINEX]\n";
    file << "detectVersion = " << (rinexSettings.detectVersion ? "true" : "false") << "\n";
    file << "gnssSystems = ";
    for (size_t i = 0; i < rinexSettings.gnssSystems.size(); ++i) {
        file << rinexSettings.gnssSystems[i];
        if (i < rinexSettings.gnssSystems.size() - 1) {
            file << ",";
        }
    }
    file << "\n";
    // Write observation codes and frequencies
    
    // Write orbit settings
    file << "\n[Orbit]\n";
    file << "orbitType = " << static_cast<int>(orbitSettings.orbitType) << "\n";
    file << "sp3File = " << orbitSettings.sp3File << "\n";
    file << "applyRelativisticCorrection = " << (orbitSettings.applyRelativisticCorrection ? "true" : "false") << "\n";
    file << "useIGSPreciseClocks = " << (orbitSettings.useIGSPreciseClocks ? "true" : "false") << "\n";
    
    // Write preprocessing settings
    file << "\n[Preprocessing]\n";
    file << "minElevation = " << preprocessingSettings.minElevation << "\n";
    file << "minSnr = " << preprocessingSettings.minSnr << "\n";
    file << "minSatelliteCount = " << preprocessingSettings.minSatelliteCount << "\n";
    file << "cycleSlipMethod = " << static_cast<int>(preprocessingSettings.cycleSlipMethod) << "\n";
    //file << "cycleSlipThreshold = " << preprocessingSettings.cycleSlipThreshold << "\n";
    file << "enableMultipathDetection = " << (preprocessingSettings.enableMultipathDetection ? "true" : "false") << "\n";
    file << "multipathStrategy = " << static_cast<int>(preprocessingSettings.multipathStrategy) << "\n";
    file << "multipathThreshold = " << preprocessingSettings.multipathThreshold << "\n";
    file << "weightingMode = " << static_cast<int>(preprocessingSettings.weightingMode) << "\n";
    file << "gfThreshold = " << preprocessingSettings.gfThreshold << "\n";
    file << "mwThreshold = " << preprocessingSettings.mwThreshold << "\n";
    file << "tdThreshold = " << preprocessingSettings.tdThreshold << "\n";
    file << "ionoRateThreshold = " << preprocessingSettings.ionoRateThreshold << "\n";
    file << "turboEditStrategy = " << static_cast<int>(preprocessingSettings.turboEditStrategy) << "\n";
    file << "adaptiveQualityThreshold = " << preprocessingSettings.adaptiveQualityThreshold << "\n";
    file << "fixCycleSlips = " << (preprocessingSettings.fixCycleSlips ? "true" : "false") << "\n";
    file << "maxConsecutiveSlips = " << preprocessingSettings.maxConsecutiveSlips << "\n";
    file << "maxTimeGap = " << preprocessingSettings.maxTimeGap << "\n";
    
    // Continue with all other sections
    
    file.close();
    return true;
}

void Config::printConfig() const {
    std::cout << "======== GNSS VTEC Estimation Configuration ========" << std::endl;
    
    std::cout << "\n--- General Settings ---" << std::endl;
    std::cout << "Verbose mode: " << (verbose ? "Yes" : "No") << std::endl;
    std::cout << "Number of threads: " << numThreads << std::endl;
    std::cout << "Output directory: " << outputDir << std::endl;
    
    std::cout << "\n--- RINEX Settings ---" << std::endl;
    std::cout << "Auto-detect RINEX version: " << (rinexSettings.detectVersion ? "Yes" : "No") << std::endl;
    std::cout << "GNSS Systems: ";
    for (const auto& sys : rinexSettings.gnssSystems) {
        std::cout << sys << " ";
    }
    std::cout << std::endl;
    
    std::cout << "\n--- Preprocessing Settings ---" << std::endl;
    std::cout << "Minimum elevation: " << preprocessingSettings.minElevation << " degrees" << std::endl;
    std::cout << "Cycle slip detection method: " << static_cast<int>(preprocessingSettings.cycleSlipMethod) << std::endl;
    std::cout << "Multipath detection: " << (preprocessingSettings.enableMultipathDetection ? "Enabled" : "Disabled") << std::endl;
    std::cout << "Weighting mode: " << static_cast<int>(preprocessingSettings.weightingMode) << std::endl;
    
    // Continue with all other sections
    
    std::cout << "====================================================" << std::endl;
}

bool Config::parseCommandLine(int argc, char* argv[]) {
    // First check if a config file is specified
    std::string configFile;
    
    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];
        if (arg == "-c" || arg == "--config") {
            if (i + 1 < argc) {
                configFile = argv[++i];
                break;
            }
        }
    }
    
    // If config file is specified, load it first
    if (!configFile.empty()) {
        if (!loadFromFile(configFile)) {
            std::cerr << "Warning: Failed to load config file: " << configFile << std::endl;
        }
    }
    
    // Now process command-line arguments to override config file settings
    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];
        
        // Handle all command-line options as before, but now they override config file values
        
        // Handle observation files (can be specified multiple times)
        if (arg == "--obs" || arg == "-o") {
            if (i + 1 < argc) {
                RinexSettings::FileConfig fileConfig;
                fileConfig.path = argv[++i];
                
                // If the next argument starts with '--' or '-', it's not a system list
                if (i + 1 < argc && argv[i+1][0] != '-') {
                    // Split the systems string
                    std::string systemsStr = argv[++i];
                    std::istringstream iss(systemsStr);
                    std::string system;
                    while (std::getline(iss, system, ',')) {
                        fileConfig.enabledSystems.push_back(system);
                    }
                }
                
                rinexSettings.observationFiles.push_back(fileConfig);
            }
            continue;
        }
        
        // Handle navigation files (can be specified multiple times)
        if (arg == "--nav" || arg == "-n") {
            if (i + 1 < argc) {
                RinexSettings::FileConfig fileConfig;
                fileConfig.path = argv[++i];
                rinexSettings.navigationFiles.push_back(fileConfig);
            }
            continue;
        }
        
        // Handle global system selection
        if (arg == "--systems" || arg == "-s") {
            if (i + 1 < argc) {
                std::string systemsStr = argv[++i];
                rinexSettings.gnssSystems.clear();
                
                std::istringstream iss(systemsStr);
                std::string system;
                while (std::getline(iss, system, ',')) {
                    rinexSettings.gnssSystems.push_back(system);
                }
            }
            continue;
        }
        
        // Handle help flag
        if (arg == "-h" || arg == "--help") {
            std::cout << "Usage: " << argv[0] << " [options]" << std::endl;
            std::cout << "Options:" << std::endl;
            std::cout << "  -h, --help                 Show this help message" << std::endl;
            std::cout << "  -c, --config <file>        Load configuration from file" << std::endl;
            std::cout << "  -v, --verbose              Enable verbose output" << std::endl;
            std::cout << "  -t, --threads <num>        Set number of threads" << std::endl;
            std::cout << "  -o, --output <dir>         Set output directory" << std::endl;
            std::cout << "  --system <sys1,sys2,...>   Set GNSS systems to process" << std::endl;
            std::cout << "  --sp3 <file>               Set SP3 precise orbit file" << std::endl;
            std::cout << "  --elev <degrees>           Set minimum elevation angle" << std::endl;
            std::cout << "  --cs-method <method>       Set cycle slip detection method" << std::endl;
            std::cout << "                             (0=None, 1=GF, 2=TD, 3=IR, 4=MW, 5=All)" << std::endl;
            std::cout << "  --multipath                Enable multipath detection/mitigation" << std::endl;
            std::cout << "  --no-multipath             Disable multipath detection/mitigation" << std::endl;
            std::cout << "  --weight-mode <mode>       Set observation weighting mode" << std::endl;
            std::cout << "                             (0=Uniform, 1=Elevation, 2=SNR, 3=Multipath, 4=Combined)" << std::endl;
            // Add more options as needed
            return false;
        }
        
        // Handle verbose mode
        if (arg == "-v" || arg == "--verbose") {
            verbose = true;
            continue;
        }
        
        // Handle number of threads
        if (arg == "-t" || arg == "--threads") {
            if (i + 1 < argc) {
                numThreads = std::stoi(argv[++i]);
            }
            continue;
        }
        
        // Handle output directory
        if (arg == "-o" || arg == "--output") {
            if (i + 1 < argc) {
                outputDir = argv[++i];
            }
            continue;
        }
        
        // Handle SP3 file
        if (arg == "--sp3") {
            if (i + 1 < argc) {
                orbitSettings.sp3File = argv[++i];
                orbitSettings.orbitType = OrbitType::Precise;
            }
            continue;
        }
        
        // Handle minimum elevation
        if (arg == "--elev") {
            if (i + 1 < argc) {
                preprocessingSettings.minElevation = std::stod(argv[++i]);
            }
            continue;
        }
        
        // Handle cycle slip method
        if (arg == "--cs-method") {
            if (i + 1 < argc) {
                int method = std::stoi(argv[++i]);
                preprocessingSettings.cycleSlipMethod = static_cast<CycleSlipMethod>(method);
            }
            continue;
        }
        
        // Handle multipath detection
        if (arg == "--multipath") {
            preprocessingSettings.enableMultipathDetection = true;
            continue;
        }
        
        if (arg == "--no-multipath") {
            preprocessingSettings.enableMultipathDetection = false;
            continue;
        }
        
        // Handle weighting mode
        if (arg == "--weight-mode") {
            if (i + 1 < argc) {
                int mode = std::stoi(argv[++i]);
                preprocessingSettings.weightingMode = static_cast<WeightingMode>(mode);
            }
            continue;
        }
        
        // Handle MW threshold
        if (arg == "--mw-threshold") {
            if (i + 1 < argc) {
                preprocessingSettings.mwThreshold = std::stod(argv[++i]);
            }
            continue;
        }
        
        // Handle GF threshold
        if (arg == "--gf-threshold") {
            if (i + 1 < argc) {
                preprocessingSettings.gfThreshold = std::stod(argv[++i]);
            }
            continue;
        }
        
        // Handle TurboEdit strategy
        if (arg == "--turbo-strategy") {
            if (i + 1 < argc) {
                int strategy = std::stoi(argv[++i]);
                preprocessingSettings.turboEditStrategy = 
                    static_cast<PreprocessingSettings::TurboEditStrategy>(strategy);
            }
            continue;
        }
        
        // Add more option handlers as needed
    }
    
    return true;
} 